#include "tlog_internal.h"

#include <memory.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <unistd.h>

void *
writer_fn(void *arg)
{
	char *lvlname;
	char buf[1024];
	size_t len;
	struct tm ts;
	int rp;
	struct tlog_instance *inst =
		(struct tlog_instance *)arg;

	while (1) {
		/* Lock ptr mutex */
		pthread_mutex_lock(&inst->ptrs_mutex);
		/* Check if the queue is empty */
		while (inst->read_ptr == inst->write_ptr) {
			/* Are we done? */
			if (inst->shutdown) {
				pthread_mutex_unlock(&inst->ptrs_mutex);
				return EXIT_SUCCESS;
			}
			/* Atomic unlock & wait for condition */
			pthread_cond_wait(&inst->not_empty_cond,
				&inst->ptrs_mutex);
		}
		/* Unlock mutex, only we can alter that condition */
		pthread_mutex_unlock(&inst->ptrs_mutex);
		rp = inst->read_ptr;
		
		/* Wait for log entry completion */
		pthread_mutex_lock(&inst->ring[rp].lock);
		pthread_mutex_unlock(&inst->ring[rp].lock);
		
		/* No mutexes held, take our time writing */
		switch (inst->ring[rp].level) {
			case LL_FATAL :
				lvlname = "FATAL";
				break;
			case LL_ERROR :
				lvlname = "ERROR";
				break;
			case LL_WARNING :
				lvlname = "WARNING";
				break;
			case LL_INFO :
				lvlname = "INFO";
				break;
			case LL_DEBUG :
				lvlname = "DEBUG";
				break;
			case LL_TRACE :
				lvlname = "TRACE";
				break;
			default:
				lvlname = "CUSTOM";
		}
		localtime_r(&inst->ring[rp].timestamp.tv_sec, &ts);
		len = strftime(buf, 1024,
			"[%Y-%m-%d %H:%M:%S:",
			&ts);
		if (len > 1023) {
			len = 1023;
		}
		write(inst->fd, buf, len + 1);
		len = snprintf(buf, 1024,
			"%.9li "
			"TID%lu"
			" %s]: ",
			inst->ring[rp].timestamp.tv_nsec,
			inst->ring[rp].id,
			lvlname);
		if (len > 1023) {
			len = 1023;
		}
		write(inst->fd, buf, len + 1);
		write(inst->fd, inst->ring[rp].buffer,
			strlen(inst->ring[rp].buffer) + 1);
		write(inst->fd, "\n", strlen("\n") + 1);
		
		/* Done writing, move read_ptr */
		rp = (rp + 1) % inst->opts.log_queue_size;
		pthread_mutex_lock(&inst->ptrs_mutex);
		inst->read_ptr = rp;

		/* If someone is waiting, kick him */
		pthread_cond_signal(&inst->not_full_cond);
		
		/* Release lock */
		pthread_mutex_unlock(&inst->ptrs_mutex);
	}
}

struct tlog_instance *
tlog_init (int fd, struct tlog_options *opts)
{
	struct tlog_options default_opts;
	struct tlog_instance *inst;
	int i;
	
	if (!opts) {
		opts = &default_opts;
		opts->max_message_size = 1024;
		opts->log_queue_size = 32;
	}
	inst = malloc(sizeof(struct tlog_instance));
	inst->shutdown = 0;
	memcpy(&inst->opts, opts, sizeof(struct tlog_options));
	inst->ring = (struct tlog_entry *)calloc(
		opts->log_queue_size,
		sizeof(struct tlog_entry));
	for (i = 0; i < opts->log_queue_size; i++) {
		inst->ring[i].buffer = malloc(opts->max_message_size);
		pthread_mutex_init(&inst->ring[i].lock, NULL);
	}
	inst->read_ptr = 0;
	inst->write_ptr = 0;
	inst->fd = fd;
	pthread_mutex_init(&inst->ptrs_mutex, NULL);
	pthread_cond_init(&inst->not_empty_cond, NULL);
	pthread_cond_init(&inst->not_full_cond, NULL);
	pthread_create(&inst->writer, NULL, writer_fn, inst);
	
	return inst;
}

void
tlog_cleanup (struct tlog_instance *inst)
{	
	int i;
        
	/* Lock ptr mutex */
	pthread_mutex_lock(&inst->ptrs_mutex);
	
	/* Shutdown */
	inst->shutdown = 1;
	
	/* In case we are sleeping */
	pthread_cond_signal(&inst->not_empty_cond);

	/* Unlock mutex */
	pthread_mutex_unlock(&inst->ptrs_mutex);
	
	/* Kill the thread */
	pthread_join(inst->writer, NULL);
	
	/* Flush the file */
	fsync(inst->fd);
	
        /* Destroy sync devices */
	pthread_cond_destroy(&inst->not_full_cond);
	pthread_cond_destroy(&inst->not_empty_cond);
	pthread_mutex_destroy(&inst->ptrs_mutex);

	/* Free memory & destroy mutexes */
       	for (i = 0; i < inst->opts.log_queue_size; i++) {
		free(inst->ring[i].buffer);
		pthread_mutex_destroy(&inst->ring[i].lock);
	}
	free(inst->ring);
	free(inst);
}

void tlog_log(struct tlog_instance *inst, int level, const char *message)
{
	int wp;
	
	/* Lock ptr mutex */
	pthread_mutex_lock(&inst->ptrs_mutex);
	/* Check if the queue is full */
	while ( ((inst->write_ptr+1)%inst->opts.log_queue_size) ==
		   inst->read_ptr ) {
		/* Atomic unlock & wait for condition */
		pthread_cond_wait(&inst->not_full_cond,
						  &inst->ptrs_mutex);
		/* Mutex relock is not atomic, need to recheck */
	}
	/* Grab the slot */
	wp = inst->write_ptr;
	pthread_mutex_lock(&inst->ring[wp].lock);
	inst->write_ptr = (wp + 1) % inst->opts.log_queue_size;
	
	/* If someone is waiting, kick him */
	pthread_cond_signal(&inst->not_empty_cond);
	
	/* Unlock mutex */
	pthread_mutex_unlock(&inst->ptrs_mutex);
	
	/* Holding mutex for log entry only */
	clock_gettime(CLOCK_REALTIME, &inst->ring[wp].timestamp);
	inst->ring[wp].id = pthread_self();
	inst->ring[wp].level = level;
	
	strncpy(inst->ring[wp].buffer, message, inst->opts.max_message_size);
	
	/* Done writing, release mutex */
	pthread_mutex_unlock(&inst->ring[wp].lock);
}
